package de.myreality.galacticum.shading;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.HashMap;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ARBShaderObjects;
import org.lwjgl.opengl.ARBVertexShader;
import org.lwjgl.opengl.ContextCapabilities;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GLContext;
import org.newdawn.slick.Color;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Vector2f;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;

/**
 * A simple wrapper utility for creating and reusing shaders and shader
 * programs.
 * 
 * @author davedes
 */
public class ShaderProgram {

	/** The vertex shader type (GL20.GL_VERTEX_SHADER). */
	public static final int VERTEX_SHADER = GL20.GL_VERTEX_SHADER;
	/** The fragment shader type (GL20.GL_FRAGMENT_SHADER). */
	public static final int FRAGMENT_SHADER = GL20.GL_FRAGMENT_SHADER;

	private static boolean strict = true;

	/**
	 * Returns true if GLSL shaders are supported in hardware on this system.
	 * This checks for the following OpenGL extensions: GL_ARB_shader_objects,
	 * GL_ARB_vertex_shader, GL_ARB_fragment_shader
	 * 
	 * @return true if shaders are supported
	 */
	public static boolean isSupported() {
		ContextCapabilities c = GLContext.getCapabilities();
		return c.GL_ARB_shader_objects && c.GL_ARB_vertex_shader
				&& c.GL_ARB_fragment_shader;
	}

	/**
	 * Whether shader programs are to use "strict" uniform/attribute name
	 * checking. That is, when strict mode is enabled, trying to modify or
	 * retrieve uniform/attribute data by name will fail and throw an
	 * IllegalArgumentException if there exists no 'active' uniforms/attributes
	 * by the given name. (In GLSL, declared uniforms might still be "inactive"
	 * if they are not used.) If strict mode is disabled, getting/setting
	 * uniform/attribute data will fail silently if the name is not found.
	 * 
	 * @param enabled
	 *            true to enable strict mode
	 */
	public static void setStrictMode(boolean enabled) {
		strict = enabled;
	}

	/**
	 * Returns <tt>true</tt> if shader programs are to use "strict"
	 * uniform/attribute name checking. That is, when strict mode is enabled,
	 * trying to modify or retrieve uniform/attribute data by name will fail and
	 * throw an IllegalArgumentException if there exists no 'active'
	 * uniforms/attributes by the given name. (In GLSL, declared uniforms might
	 * still be "inactive" if they are not used.) If strict mode is disabled,
	 * getting/setting uniform/attribute data will fail silently if the name is
	 * not found.
	 * 
	 * @return true if strict mode is enabled
	 */
	public static boolean isStrictMode() {
		return strict;
	}

	/**
	 * Disables shaders.
	 */
	public static void unbindAll() {
		ARBShaderObjects.glUseProgramObjectARB(0);
	}

	/** The OpenGL handle for this shader program object. */
	protected int program;
	/** The log for this program. */
	protected String log = "";
	/** A map of uniforms by <name, int>. */
	protected HashMap<String, Integer> uniforms = new HashMap<String, Integer>();
	/** A map of attributes by <name, int>. */
	protected HashMap<String, Integer> attributes = new HashMap<String, Integer>();
	/** The vertex shader source. */
	protected String vertShaderSource;
	/** The fragment shader source. */
	protected String fragShaderSource;
	/** The OpenGL handle for this program's vertex shader object. */
	protected int vert;
	/** The OpenGL handle for this program's fragment shader object. */
	protected int frag;

	private FloatBuffer buf4;
	private IntBuffer ibuf4;

	/**
	 * A convenience method to load a ShaderProgram from two text files.
	 * 
	 * @param vertFile
	 *            the location of the vertex shader source
	 * @param fragFile
	 *            the location of the frag shader source
	 * @return the compiled and linked ShaderProgram
	 * @throws SlickException
	 *             if there was an issue reading the file, compiling the source,
	 *             or linking the program
	 */
	public static ShaderProgram loadProgram(String vertFile, String fragFile)
			throws SlickException {
		return new ShaderProgram(readFile(vertFile), readFile(fragFile));
	}

	/**
	 * Loads the given input stream into a source code string.
	 * 
	 * @param ref
	 *            the location of the text file
	 * @return the resulting source code String
	 * @throws SlickException
	 *             if there was an issue reading the source
	 */
	public static String readFile(String ref) throws SlickException {
		InputStream in = ResourceLoader.getResourceAsStream(ref);
		try {
			return readFile(in);
		} catch (SlickException e) {
			throw new SlickException("could not load source file: " + ref);
		}
	}

	/**
	 * Loads the given input stream into a source code string.
	 * 
	 * @param in
	 *            the input stream
	 * @return the resulting source code String
	 * @throws SlickException
	 *             if there was an issue reading the source
	 * @author Nitram
	 */
	public static String readFile(InputStream in) throws SlickException {
		try {
			final StringBuffer sBuffer = new StringBuffer();
			final BufferedReader br = new BufferedReader(new InputStreamReader(
					in));
			final char[] buffer = new char[1024];

			int cnt;
			while ((cnt = br.read(buffer, 0, buffer.length)) > -1) {
				sBuffer.append(buffer, 0, cnt);
			}
			br.close();
			return sBuffer.toString();
		} catch (IOException e) {
			throw new SlickException("could not load source file");
		}
	}

	/**
	 * Creates a new shader program with the given vertex and fragment shader
	 * source code. The given source code is compiled, then the shaders attached
	 * and linked.
	 * 
	 * If shaders are not supported on this system (isSupported returns false),
	 * a SlickException will be thrown.
	 * 
	 * If one of the shaders does not compile successfully, a SlickException
	 * will be thrown.
	 * 
	 * If there was a problem in linking the shaders to the program, a
	 * SlickException will be thrown and the program will be deleted.
	 * 
	 * @param vertexShaderSource
	 *            the shader code to compile, attach and link
	 * @param fragShaderSource
	 *            the frag code to compile, attach and link
	 * @throws SlickException
	 *             if there was an issue
	 * @throws IllegalArgumentException
	 *             if there was an issue
	 */
	public ShaderProgram(String vertexShaderSource, String fragShaderSource)
			throws SlickException {
		if (vertexShaderSource == null || fragShaderSource == null)
			throw new IllegalArgumentException("shader source must be non-null");
		if (!isSupported())
			throw new SlickException(
					"no shader support found; driver does not support extension GL_ARB_shader_objects");

		this.vertShaderSource = vertexShaderSource;
		this.fragShaderSource = fragShaderSource;
		vert = compileShader(VERTEX_SHADER, vertexShaderSource);
		frag = compileShader(FRAGMENT_SHADER, fragShaderSource);
		program = createProgram();
		try {
			linkProgram();
		} catch (SlickException e) {
			release();
			throw e;
		}
		if (log != null && log.length() != 0)
			Log.warn("GLSL Info: " + log);
	}

	/**
	 * Subclasses may wish to implement this to manually handle program/shader
	 * creation, compiling, and linking. This constructor does nothing; users
	 * will need to call compileShader, createProgram and linkProgram manually.
	 * 
	 * @throws SlickException
	 */
	protected ShaderProgram() {
	}

	/**
	 * Creates a shader program and returns its OpenGL handle. If the result is
	 * zero, an exception will be thrown.
	 * 
	 * @return the OpenGL handle for the newly created shader program
	 * @throws SlickException
	 *             if the result is zero
	 */
	protected int createProgram() throws SlickException {
		if (!isSupported())
			throw new SlickException(
					"no shader support found; driver does not support extension GL_ARB_shader_objects");
		int program = ARBShaderObjects.glCreateProgramObjectARB();
		if (program == 0)
			throw new SlickException(
					"could not create program; check ShaderProgram.isSupported()");
		return program;
	}

	private String shaderTypeString(int type) {
		if (type == FRAGMENT_SHADER)
			return "FRAGMENT_SHADER";
		else if (type == VERTEX_SHADER)
			return "VERTEX_SHADER";
		else if (type == GL32.GL_GEOMETRY_SHADER)
			return "GEOMETRY_SHADER";
		else
			return "shader";
	}

	/**
	 * Compiles a shader from source and returns its handle. If the compilation
	 * failed, a SlickException will be thrown. If the compilation had error,
	 * info or warnings messages, they will be appended to this program's log.
	 * 
	 * @param type
	 *            the type to use in compilation
	 * @param source
	 *            the source code to compile
	 * @return the resulting ID
	 * @throws SlickException
	 *             if compilation was unsuccessful
	 */
	protected int compileShader(int type, String source) throws SlickException {
		int shader = ARBShaderObjects.glCreateShaderObjectARB(type);
		if (shader == 0)
			throw new SlickException(
					"could not create shader object; check ShaderProgram.isSupported()");
		ARBShaderObjects.glShaderSourceARB(shader, source);
		ARBShaderObjects.glCompileShaderARB(shader);
		int comp = ARBShaderObjects.glGetObjectParameteriARB(shader,
				GL20.GL_COMPILE_STATUS);
		int len = ARBShaderObjects.glGetObjectParameteriARB(shader,
				GL20.GL_INFO_LOG_LENGTH);
		String t = shaderTypeString(type);
		String err = ARBShaderObjects.glGetInfoLogARB(shader, len);
		if (err != null && err.length() != 0)
			log += t + " compile log:\n" + err + "\n";
		if (comp == GL11.GL_FALSE)
			throw new SlickException(log);
		return shader;
	}

	/**
	 * Called to attach vertex and fragment; users may override this for more
	 * specific purposes.
	 */
	protected void attachShaders() {
		ARBShaderObjects.glAttachObjectARB(getID(), vert);
		ARBShaderObjects.glAttachObjectARB(getID(), frag);
	}

	/**
	 * Calls attachShaders and links the program.
	 * 
	 * @throws SlickException
	 *             if this program is invalid (released) or if the link was
	 *             unsuccessful
	 */
	protected void linkProgram() throws SlickException {
		if (!valid())
			throw new SlickException(
					"trying to link an invalid (i.e. released) program");

		uniforms.clear();
		attributes.clear();

		attachShaders();

		ARBShaderObjects.glLinkProgramARB(program);
		int comp = ARBShaderObjects.glGetObjectParameteriARB(program,
				GL20.GL_LINK_STATUS);
		int len = ARBShaderObjects.glGetObjectParameteriARB(program,
				GL20.GL_INFO_LOG_LENGTH);
		String err = ARBShaderObjects.glGetInfoLogARB(program, len);
		if (err != null && err.length() != 0)
			log = err + "\n" + log;
		if (log != null)
			log = log.trim();
		if (comp == GL11.GL_FALSE)
			throw new SlickException(log);

		fetchUniforms();
		fetchAttributes();
	}

	/**
	 * Returns the full log of compiling/linking errors, info, warnings, etc.
	 * 
	 * @return the full log of this ShaderProgram
	 */
	public String getLog() {
		return log;
	}

	/**
	 * Enables this shader for use -- only one shader can be bound at a time.
	 * Calling bind() when another program is bound will simply make this object
	 * the active program.
	 * 
	 * @throw IllegalStateException if this program is invalid
	 */
	public void bind() {
		if (!valid())
			throw new IllegalStateException(
					"trying to enable a program that is not valid");
		ARBShaderObjects.glUseProgramObjectARB(program);
	}

	/**
	 * Unbinds all shaders; this is the equivalent of ShaderProgram.unbindAll(),
	 * and only included for consistency with bind() and the rest of the API
	 * (i.e. startUse/endUse). Users do not need to unbind one shader before
	 * binding a new one.
	 */
	public void unbind() {
		ShaderProgram.unbindAll();
	}

	/**
	 * Disables shaders (unbind), then detaches and releases the shaders
	 * associated with this program. This can be called after linking a program
	 * in order to free up memory (as the shaders are no longer needed),
	 * however, since it is not a commonly used feature and thus not well tested
	 * on all drivers, it should be used with caution. Shaders shouldn't be used
	 * after being released.
	 */
	public void releaseShaders() {
		unbind();
		if (vert != 0) {
			ARBShaderObjects.glDetachObjectARB(getID(), vert);
			ARBShaderObjects.glDeleteObjectARB(vert);
			vert = 0;
		}
		if (frag != 0) {
			ARBShaderObjects.glDetachObjectARB(getID(), frag);
			ARBShaderObjects.glDeleteObjectARB(frag);
			frag = 0;
		}
	}

	/**
	 * If this program has not yet been released, this will disable shaders
	 * (unbind), then releases this program and its shaders. To only release the
	 * shaders (not the program itself), call releaseShaders(). Programs will be
	 * considered "invalid" after being released, and should no longer be used.
	 */
	public void release() {
		if (program != 0) {
			unbind();
			releaseShaders();
			ARBShaderObjects.glDeleteObjectARB(program);
			program = 0;
		}
	}

	/**
	 * Returns the OpenGL handle for this program's vertex shader.
	 * 
	 * @return the vertex ID
	 */
	public int getVertexShaderID() {
		return vert;
	}

	/**
	 * Returns the OpenGL handle for this program's fragment shader.
	 * 
	 * @return the fragment ID
	 */
	public int getFragmentShaderID() {
		return frag;
	}

	/**
	 * Returns the source code for the vertex shader.
	 * 
	 * @return the source code
	 */
	public String getVertexShaderSource() {
		return vertShaderSource;
	}

	/**
	 * Returns the source code for the fragment shader.
	 * 
	 * @return the source code
	 */
	public String getFragmentShaderSource() {
		return fragShaderSource;
	}

	/**
	 * Returns the OpenGL handle for this shader program
	 * 
	 * @return the program ID
	 */
	public int getID() {
		return program;
	}

	/**
	 * A shader program is "valid" if it's ID is not zero. Upon releasing a
	 * program, the ID will be set to zero.
	 * 
	 * @return whether this program is valid
	 */
	public boolean valid() {
		return program != 0;
	}

	private void fetchUniforms() {
		int len = ARBShaderObjects.glGetObjectParameteriARB(program,
				GL20.GL_ACTIVE_UNIFORMS);
		// max length of all uniforms stored in program
		int strLen = ARBShaderObjects.glGetObjectParameteriARB(program,
				GL20.GL_ACTIVE_UNIFORM_MAX_LENGTH);

		for (int i = 0; i < len; i++) {
			String name = ARBShaderObjects.glGetActiveUniformARB(program, i,
					strLen);
			int id = ARBShaderObjects.glGetUniformLocationARB(program, name);
			uniforms.put(name, id);
		}
	}

	private void fetchAttributes() {
		int len = ARBShaderObjects.glGetObjectParameteriARB(program,
				GL20.GL_ACTIVE_ATTRIBUTES);
		// max length of all attributes stored in program
		int strLen = ARBShaderObjects.glGetObjectParameteriARB(program,
				GL20.GL_ACTIVE_ATTRIBUTE_MAX_LENGTH);
		for (int i = 0; i < len; i++) {
			String name = ARBVertexShader.glGetActiveAttribARB(program, i,
					strLen);
			int id = ARBVertexShader.glGetAttribLocationARB(program, name);
			attributes.put(name, id);
		}
	}

	/**
	 * Returns the ID of the given uniform.
	 * 
	 * @param name
	 *            the uniform name
	 * @return the ID (location) in the shader program
	 */
	public int getUniformID(String name) {
		Integer locI = uniforms.get(name);
		int location = locI == null ? -1 : locI.intValue();
		if (location != -1)
			return location;
		location = ARBShaderObjects.glGetUniformLocationARB(program, name);
		if (location == -1 && strict)
			throw new IllegalArgumentException("no active uniform by name '"
					+ name
					+ "' (disable strict compiling to suppress warnings)");
		uniforms.put(name, location);
		return location;
	}

	/**
	 * Returns the ID of the given attribute.
	 * 
	 * @param name
	 *            the attribute name
	 * @return the ID (location) in the shader program
	 */
	public int getAttributeID(String name) {
		int location = attributes.get(name);
		if (location != -1)
			return location;
		location = ARBVertexShader.glGetAttribLocationARB(program, name);
		if (location == -1 && strict)
			throw new IllegalArgumentException("no active attribute by name '"
					+ name + "'");
		attributes.put(name, location);
		return location;
	}

	/**
	 * Returns the names of all active attributes that were found when linking
	 * the program.
	 * 
	 * @return an array list of active attribute names
	 */
	public String[] getAttributes() {
		return attributes.keySet().toArray(new String[attributes.size()]);
	}

	/**
	 * Returns the names of all active uniforms that were found when linking the
	 * program.
	 * 
	 * @return an array list of active uniform names
	 */
	public String[] getUniformNames() {
		return uniforms.keySet().toArray(new String[uniforms.size()]);
	}

	/**
	 * Enables the vertex array -- in strict mode, if the vertex attribute is
	 * not found (or it's inactive), an IllegalArgumentException will be thrown.
	 * If strict mode is disabled and the vertex attribute is not found, this
	 * method will return <tt>false</tt> otherwise it will return <tt>true</tt>.
	 * 
	 * @param name
	 *            the name of the vertex attribute to enable
	 * @return false if strict mode is disabled and this attribute couldn't be
	 *         found
	 */
	public boolean enableVertexAttribute(String name) {
		int id = getAttributeID(name);
		if (id == -1)
			return false;
		ARBVertexShader.glEnableVertexAttribArrayARB(id);
		return true;
	}

	/**
	 * Disables the vertex array -- in strict mode, if the vertex attribute is
	 * not found (or it's inactive), an IllegalArgumentException will be thrown.
	 * If strict mode is disabled and the vertex attribute is not found, this
	 * method will return <tt>false</tt> otherwise it will return <tt>true</tt>.
	 * 
	 * @param name
	 *            the name of the vertex attribute to disable
	 * @return false if strict mode is disabled and this attribute couldn't be
	 *         found
	 */
	public boolean disableVertexAttribute(String name) {
		int id = getAttributeID(name);
		if (id == -1)
			return false;
		ARBVertexShader.glDisableVertexAttribArrayARB(id);
		return true;
	}

	// public void setVertexAttribute(String name, int size, int type, boolean
	// normalize, int stride, FloatBuffer buffer) {
	// ARBVertexShader.glVertexAttrib
	// }

	/**
	 * Sets the value of an RGBA vec4 uniform to the given color
	 * 
	 * @param name
	 *            the RGBA vec4 uniform
	 * @param color
	 *            the color to assign
	 */
	public void setUniform4f(String name, Color color) {
		setUniform4f(name, color.r, color.g, color.b, color.a);
	}

	/**
	 * Sets the value of a vec2 uniform to the given Vector2f.
	 * 
	 * @param name
	 *            the vec2 uniform
	 * @param vec
	 *            the vector to use
	 */
	public void setUniform2f(String name, Vector2f vec) {
		setUniform2f(name, vec.x, vec.y);
	}

	private FloatBuffer uniformf(String name) {
		if (buf4 == null)
			buf4 = BufferUtils.createFloatBuffer(4);
		buf4.clear();
		getUniform(name, buf4);
		return buf4;
	}

	private IntBuffer uniformi(String name) {
		// TODO: add setters/getters for ivec2, ivec3, ivec4
		if (ibuf4 == null)
			ibuf4 = BufferUtils.createIntBuffer(4);
		ibuf4.clear();
		getUniform(name, ibuf4);
		return ibuf4;
	}

	/**
	 * A convenience method to retrieve an integer/sampler2D uniform.
	 * 
	 * @param name
	 *            the uniform name
	 * @return the value
	 */
	public int getUniform1i(String name) {
		return uniformi(name).get(0);
	}

	/**
	 * A convenience method to retrieve an ivec2 uniform; for maximum
	 * performance and memory efficiency you should use getUniform(String,
	 * IntBuffer) with a shared buffer.
	 * 
	 * @param name
	 *            the name of the uniform
	 * @return a newly created int[] array with 2 elements; e.g. (x, y)
	 */
	public int[] getUniform2i(String name) {
		IntBuffer buf = uniformi(name);
		return new int[] { buf.get(0), buf.get(1) };
	}

	/**
	 * A convenience method to retrieve an ivec3 uniform; for maximum
	 * performance and memory efficiency you should use getUniform(String,
	 * IntBuffer) with a shared buffer.
	 * 
	 * @param name
	 *            the name of the uniform
	 * @return a newly created int[] array with 3 elements; e.g. (x, y, z)
	 */
	public int[] getUniform3i(String name) {
		IntBuffer buf = uniformi(name);
		return new int[] { buf.get(0), buf.get(1), buf.get(2) };
	}

	/**
	 * A convenience method to retrieve an ivec4 uniform; for maximum
	 * performance and memory efficiency you should use getUniform(String,
	 * IntBuffer) with a shared buffer.
	 * 
	 * @param name
	 *            the name of the uniform
	 * @return a newly created int[] array with 2 elements; e.g. (r, g, b, a)
	 */
	public int[] getUniform4i(String name) {
		IntBuffer buf = uniformi(name);
		return new int[] { buf.get(0), buf.get(1), buf.get(2), buf.get(3) };
	}

	/**
	 * A convenience method to retrieve a float uniform.
	 * 
	 * @param name
	 *            the uniform name
	 * @return the value
	 */
	public float getUniform1f(String name) {
		return uniformf(name).get(0);
	}

	/**
	 * A convenience method to retrieve a vec2 uniform; for maximum performance
	 * and memory efficiency you should use getUniform(String, FloatBuffer) with
	 * a shared buffer.
	 * 
	 * @param name
	 *            the name of the uniform
	 * @return a newly created float[] array with 2 elements; e.g. (x, y)
	 */
	public float[] getUniform2f(String name) {
		FloatBuffer buf = uniformf(name);
		return new float[] { buf.get(0), buf.get(1) };
	}

	/**
	 * A convenience method to retrieve a vec3 uniform; for maximum performance
	 * and memory efficiency you should use getUniform(String, FloatBuffer) with
	 * a shared buffer.
	 * 
	 * @param name
	 *            the name of the uniform
	 * @return a newly created float[] array with 3 elements; e.g. (x, y, z)
	 */
	public float[] getUniform3f(String name) {
		FloatBuffer buf = uniformf(name);
		return new float[] { buf.get(0), buf.get(1), buf.get(2) };
	}

	/**
	 * A convenience method to retrieve a vec4 uniform; for maximum performance
	 * and memory efficiency you should use getUniform(String, FloatBuffer) with
	 * a shared buffer.
	 * 
	 * @param name
	 *            the name of the uniform
	 * @return a newly created float[] array with 4 elements; e.g. (r, g, b, a)
	 */
	public float[] getUniform4f(String name) {
		FloatBuffer buf = uniformf(name);
		return new float[] { buf.get(0), buf.get(1), buf.get(2), buf.get(3) };
	}

	/**
	 * Retrieves data from a uniform and places it in the given buffer. If
	 * strict mode is enabled, this will throw an IllegalArgumentException if
	 * the given uniform is not 'active' -- i.e. if GLSL determined that the
	 * shader isn't using it. If strict mode is disabled, this method will
	 * return <tt>true</tt> if the uniform was found, and <tt>false</tt>
	 * otherwise.
	 * 
	 * @param name
	 *            the name of the uniform
	 * @param buf
	 *            the buffer to place the data
	 * @return true if the uniform was found, false if there is no active
	 *         uniform by that name
	 */
	public boolean getUniform(String name, FloatBuffer buf) {
		int id = getUniformID(name);
		if (id == -1)
			return false;
		ARBShaderObjects.glGetUniformARB(program, id, buf);
		return true;
	}

	/**
	 * Retrieves data from a uniform and places it in the given buffer. If
	 * strict mode is enabled, this will throw an IllegalArgumentException if
	 * the given uniform is not 'active' -- i.e. if GLSL determined that the
	 * shader isn't using it. If strict mode is disabled, this method will
	 * return <tt>true</tt> if the uniform was found, and <tt>false</tt>
	 * otherwise.
	 * 
	 * @param name
	 *            the name of the uniform
	 * @param buf
	 *            the buffer to place the data
	 * @return true if the uniform was found, false if there is no active
	 *         uniform by that name
	 */
	public boolean getUniform(String name, IntBuffer buf) {
		int id = getUniformID(name);
		if (id == -1)
			return false;
		ARBShaderObjects.glGetUniformARB(program, id, buf);
		return true;
	}

	/**
	 * Whether the shader program was linked with the active uniform by the
	 * given name. A uniform might be "inactive" even if it was declared at the
	 * top of a shader; if GLSL finds that a uniform isn't needed (i.e. not used
	 * in shader), then it will not be active.
	 * 
	 * @param name
	 *            the name of the uniform
	 * @return true if this shader program could find the active uniform
	 */
	public boolean hasUniform(String name) {
		return uniforms.containsKey(name);
	}

	/**
	 * Whether the shader program was linked with the active attribute by the
	 * given name. A attribute might be "inactive" even if it was declared at
	 * the top of a shader; if GLSL finds that a attribute isn't needed (i.e.
	 * not used in shader), then it will not be active.
	 * 
	 * @param name
	 *            the name of the attribute
	 * @return true if this shader program could find the active attribute
	 */
	public boolean hasAttribute(String name) {
		return attributes.containsKey(name);
	}

	/**
	 * Sets the value of a float uniform.
	 * 
	 * @param name
	 *            the uniform by name
	 * @param f
	 *            the float value
	 */
	public void setUniform1f(String name, float f) {
		int id = getUniformID(name);
		if (id == -1)
			return;
		ARBShaderObjects.glUniform1fARB(id, f);
	}

	/**
	 * Sets the value of a sampler2D uniform.
	 * 
	 * @param name
	 *            the uniform by name
	 * @param i
	 *            the integer / active texture (e.g. 0 for TEXTURE0)
	 */
	public void setUniform1i(String name, int i) {
		int id = getUniformID(name);
		if (id == -1)
			return;
		ARBShaderObjects.glUniform1iARB(id, i);
	}

	/**
	 * Sets the value of a vec2 uniform.
	 * 
	 * @param name
	 *            the uniform by name
	 * @param a
	 *            vec.x / tex.s
	 * @param b
	 *            vec.y / tex.t
	 */
	public void setUniform2f(String name, float a, float b) {
		int id = getUniformID(name);
		if (id == -1)
			return;
		ARBShaderObjects.glUniform2fARB(id, a, b);
	}

	/**
	 * Sets the value of a vec3 uniform.
	 * 
	 * @param name
	 *            the uniform by name
	 * @param a
	 *            vec.x / color.r / tex.s
	 * @param b
	 *            vec.y / color.g / tex.t
	 * @param c
	 *            vec.z / color.b / tex.p
	 */
	public void setUniform3f(String name, float a, float b, float c) {
		int id = getUniformID(name);
		if (id == -1)
			return;

		ARBShaderObjects.glUniform3fARB(id, a, b, c);
	}

	/**
	 * Sets the value of a vec4 uniform.
	 * 
	 * @param name
	 *            the uniform by name
	 * @param a
	 *            vec.x / color.r
	 * @param b
	 *            vec.y / color.g
	 * @param c
	 *            vec.z / color.b
	 * @param d
	 *            vec.w / color.a
	 */
	public void setUniform4f(String name, float a, float b, float c, float d) {
		int id = getUniformID(name);
		if (id == -1)
			return;
		ARBShaderObjects.glUniform4fARB(id, a, b, c, d);
	}

	/**
	 * Sets the value of a ivec2 uniform.
	 * 
	 * @param name
	 *            the uniform by name
	 * @param a
	 *            vec.x / tex.s
	 * @param b
	 *            vec.y / tex.t
	 */
	public void setUniform2i(String name, int a, int b) {
		int id = getUniformID(name);
		if (id == -1)
			return;
		ARBShaderObjects.glUniform2iARB(id, a, b);
	}

	/**
	 * Sets the value of a ivec3 uniform.
	 * 
	 * @param name
	 *            the uniform by name
	 * @param a
	 *            vec.x / color.r
	 * @param b
	 *            vec.y / color.g
	 * @param c
	 *            vec.z / color.b
	 */
	public void setUniform3i(String name, int a, int b, int c) {
		int id = getUniformID(name);
		if (id == -1)
			return;
		ARBShaderObjects.glUniform3iARB(id, a, b, c);
	}

	/**
	 * Sets the value of a ivec4 uniform.
	 * 
	 * @param name
	 *            the uniform by name
	 * @param a
	 *            vec.x / color.r
	 * @param b
	 *            vec.y / color.g
	 * @param c
	 *            vec.z / color.b
	 * @param d
	 *            vec.w / color.a
	 */
	public void setUniform4i(String name, int a, int b, int c, int d) {
		int id = getUniformID(name);
		if (id == -1)
			return;
		ARBShaderObjects.glUniform4iARB(id, a, b, c, d);
	}

	/**
	 * Sets a uniform matrix2 with the given name and transpose.
	 * 
	 * @param name
	 *            the name to use
	 * @param transpose
	 *            whether to transpose the matrix
	 * @param buf
	 *            the buffer representing the matrix2
	 */
	public void setMatrix2(String name, boolean transpose, FloatBuffer buf) {
		int id = getUniformID(name);
		if (id == -1)
			return;
		ARBShaderObjects.glUniformMatrix2ARB(id, transpose, buf);
	}

	/**
	 * Sets a uniform matrix3 with the given name and transpose.
	 * 
	 * @param name
	 *            the name to use
	 * @param transpose
	 *            whether to transpose the matrix
	 * @param buf
	 *            the buffer representing the matrix3
	 */
	public void setMatrix3(String name, boolean transpose, FloatBuffer buf) {
		int id = getUniformID(name);
		if (id == -1)
			return;
		ARBShaderObjects.glUniformMatrix3ARB(id, transpose, buf);
	}

	/**
	 * Sets a uniform matrix4 with the given name and transpose.
	 * 
	 * @param name
	 *            the name to use
	 * @param transpose
	 *            whether to transpose the matrix
	 * @param buf
	 *            the buffer representing the matrix4
	 */
	public void setMatrix4(String name, boolean transpose, FloatBuffer buf) {
		int id = getUniformID(name);
		if (id == -1)
			return;
		ARBShaderObjects.glUniformMatrix4ARB(id, transpose, buf);
	}
}